#pragma once
#include "Runtime/BattleFirePrefix.h"
#include "Runtime/Utils/ThreadSafeCounter.h"
#include "Runtime/Utils/LockFreeList.h"
#include "Event.h"
#include "Thread.h"
#include "RunnableTask.h"
#include "PoolThreadContext.h"

namespace Alice {
    struct CompositedThread
    {
        PoolThreadContext*	mThreadContext;
        Thread*	            mThread;
        bool			    bAttached;
        CompositedThread()
            : mThreadContext(nullptr)
            , mThread(nullptr)
            , bAttached(false)
        {
        }
    };
	class ThreadPoolInterface
	{
	public:
		static ThreadPoolInterface& Get();
		static void Shutdown();
		static bool IsRunning();
		virtual void Startup(int32 inNumThreads)=0;
	public:
		virtual void QueueTask(RunnableTask* Task, ENamedThreads ThreadToExecuteOn, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread) = 0;
	public:
		virtual ~ThreadPoolInterface()
		{
		}
		virtual ENamedThreads GetCurrentThreadIfKnown(bool bLocalQueue = false) = 0;
		virtual	int32 GetNumWorkerThreads() = 0;
		virtual bool IsThreadProcessingTasks(ENamedThreads ThreadToCheck) = 0;
		virtual void AttachToThread(ENamedThreads CurrentThread) = 0;
		virtual uint64 ProcessThreadUntilIdle(ENamedThreads CurrentThread) = 0;
		virtual void ProcessThreadUntilRequestReturn(ENamedThreads CurrentThread) = 0;
		virtual void ProcessThreadUntilManualStop(ENamedThreads CurrentThread) = 0;
		virtual void RequestReturn(ENamedThreads CurrentThread)=0;
		virtual void WaitUntilTasksComplete(const RunnableTaskEventArray& Tasks, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread)=0;
		virtual void WaitUntilTasksComplete(ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread) = 0;
		virtual void TriggerEventWhenTasksComplete(Event* InEvent, const RunnableTaskEventArray& Tasks, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread, ENamedThreads TriggerThread = ENamedThreads::AnyHiPriThreadHiPriTask) = 0;
		virtual void AddShutdownCallback(void(*Callback)()) = 0;
		virtual void WakeNamedThread(ENamedThreads ThreadToWake) = 0;
		void WaitUntilTaskCompletes(const RunnableTaskEventRef& Task, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread)
		{
			WaitUntilTasksComplete({ Task }, CurrentThreadIfKnown);
		}
		void WaitUntilTaskCompletes(RunnableTaskEventRef&& Task, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread)
		{
			WaitUntilTasksComplete({ std::forward<RunnableTaskEventRef>(Task) }, CurrentThreadIfKnown);
		}
		void TriggerEventWhenTaskCompletes(Event* InEvent, const RunnableTaskEventRef& Task, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread, ENamedThreads TriggerThread = ENamedThreads::AnyHiPriThreadHiPriTask)
		{
			RunnableTaskEventArray Prerequistes;
			Prerequistes.push_back(Task);
			TriggerEventWhenTasksComplete(InEvent, Prerequistes, CurrentThreadIfKnown, TriggerThread);
		}
		virtual RunnableTask* FindWork(ENamedThreads ThreadInNeed){return nullptr;}
	};
	class ThreadPool : public ThreadPoolInterface
	{
	friend class RunnableTask;
	public:
		ThreadPool();
		void Startup(int32 inNumThreads);
	protected:
		virtual void QueueTask(RunnableTask* Task, ENamedThreads ThreadToExecuteOn, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread) override;
	public:
		virtual ENamedThreads GetCurrentThreadIfKnown(bool bLocalQueue = false) override;
		virtual	int32 GetNumWorkerThreads() override;
		virtual bool IsThreadProcessingTasks(ENamedThreads ThreadToCheck) override;
		virtual void AttachToThread(ENamedThreads CurrentThread) override;
		virtual uint64 ProcessThreadUntilIdle(ENamedThreads CurrentThread) override;
		virtual void ProcessThreadUntilRequestReturn(ENamedThreads CurrentThread) override;
		virtual void ProcessThreadUntilManualStop(ENamedThreads CurrentThread) override;
		virtual void RequestReturn(ENamedThreads CurrentThread) override;
		virtual void WaitUntilTasksComplete(const RunnableTaskEventArray& Tasks, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread) override;
		virtual void WaitUntilTasksComplete(ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread) override;
		virtual void TriggerEventWhenTasksComplete(Event* InEvent, const RunnableTaskEventArray& Tasks, ENamedThreads CurrentThreadIfKnown = ENamedThreads::AnyThread, ENamedThreads TriggerThread = ENamedThreads::AnyHiPriThreadHiPriTask) override;
		virtual void AddShutdownCallback(void(*Callback)()) override;
		virtual void WakeNamedThread(ENamedThreads ThreadToWake) override;
	public:
		PoolThreadContext& ThreadContext(int32 Index)
		{
			return *CompositedThreads[Index].mThreadContext;
		}
		ENamedThreads GetCurrentThread();
		int32 ThreadIndexToPriorityIndex(int32 ThreadIndex);
		void StartTaskThread(int32 Priority, int32 IndexToStart);
		void StartAllTaskThreads(bool bDoBackgroundThreads);
		RunnableTask* FindWork(ENamedThreads ThreadInNeed);
		void StallForTuning(int32 Index, bool Stall);
		
		#define CREATE_HIPRI_TASK_THREADS (1)
		#define CREATE_BACKGROUND_TASK_THREADS (1)
		enum
		{
			/** Compile time maximum number of threads. Didn't really need to be a compile time constant, but task thread are limited by MAX_LOCK_FREE_LINKS_AS_BITS **/
			MAX_THREADS = 26 * (CREATE_HIPRI_TASK_THREADS + CREATE_BACKGROUND_TASK_THREADS + 1) + (int32)ENamedThreads::ActualRenderingThread + 1,
			MAX_THREAD_PRIORITIES = 3
		};
		/** Per thread data. **/
		CompositedThread	CompositedThreads[MAX_THREADS];
		int32				NumThreads;
		int32				NumNamedThreads;
		int32				NumTaskThreadSets;
		int32				NumTaskThreadsPerSet;
		bool				bCreatedHiPriorityThreads;
		bool				bCreatedBackgroundPriorityThreads;
		ENamedThreads		LastExternalThread;
		ThreadSafeCounter	ReentrancyCheck;
		uint32				mTLSSlot;

		std::list<void(*)()> ShutdownCallbacks;

		FStallingTaskQueue<RunnableTask, 64, 2>	IncomingAnyThreadTasks[MAX_THREAD_PRIORITIES];
	};
}